﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;

namespace GoodStuff.Data.Linq
{
    public static class KeyFromExpression
    {
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="query"></param>
        /// <returns></returns>
        /// <remarks>http://petemontgomery.wordpress.com/2008/08/07/caching-the-results-of-linq-queries/</remarks>
        public static IEnumerable<T> FromCache<T>(this IQueryable<T> query)
        {
            Expression expression = query.Expression;

            return Web.Cached.Get<IEnumerable<T>>(expression.GetKeyFromBinaryExpressionOperand(), "fromCache", TimeSpan.FromMinutes(1), () => { return query.ToList(); });
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="expression"></param>
        /// <returns></returns>
        /// <remarks>http://pastebin.com/DhHi0Cs2</remarks>
        public static string GetCacheKey<TEntity>(this Expression<Func<TEntity, bool>> expression) where TEntity : class
        {
            // We get the name of the class so it gets more unique
            string key = typeof(TEntity).Name + " ";
            if (expression.Body is BinaryExpression)
            {
                //We get the binary expression from the expression body so we can get the values
                var binaryExpression = (BinaryExpression)expression.Body;
                key = binaryExpression.GetKey(key);
            }
            else if (expression.Body is MethodCallExpression)
            {
                key += string.Format(" {0} ", ((MethodCallExpression)expression.Body).GetPropertyNameFromMethodCallExpression());
            }
            //We get rid of the trailing and double whitespace to make it look more clean
            key = key.Replace("  ", " ").TrimEnd();

            return key;
        }

        private static string GetPropertyNameFromMethodCallExpression(this MethodCallExpression methodCallExpression)
        {
            var name = string.Empty;
            if (methodCallExpression.Object is MethodCallExpression)
            {
                name = ((MethodCallExpression)methodCallExpression.Object).GetPropertyNameFromMethodCallExpression();
            }
            else if (methodCallExpression.Object is MemberExpression)
            {
                name = ((MemberExpression)methodCallExpression.Object).GetValueFromMemberExpression();
            }

            return name;
        }

        private static string GetKey(this BinaryExpression binaryExpression, string key)
        {
            //if the nodetype on the right side of the expression not is a constant we will need to work on it in order to get the values
            if (binaryExpression.Right.NodeType != ExpressionType.Constant)
            {
                key += binaryExpression.Left.GetKeyFromBinaryExpressionOperand();
                //binaryExpression.NodeType here would return for example AndAlso, OrElse and so on.
                key += string.Format("{0} ", binaryExpression.NodeType);

                key += binaryExpression.Right.GetKeyFromBinaryExpressionOperand();
            }
            //if the nodetype is an ExpresisonType of constant we can get the values
            else
            {
                //binaryExpression.Left returns for example "p.CommentId".
                //binaryExpression.NodeType returns for example "Equals", "NotEquals" and so on.
                //binaryExpression.Right contains the value for example "132"
                key += string.Format("{0} {1} {2} ",
                                     binaryExpression.Left.GetValueFromBinaryExpressionOperands(),
                                     binaryExpression.NodeType,
                                     binaryExpression.Right.GetValueFromBinaryExpressionOperands());
            }

            return key;
        }

        private static string GetKeyFromBinaryExpressionOperand(this Expression expression)
        {
            var key = string.Empty;
            if (expression is MemberExpression)
                key += string.Format(" {0} ", ((MemberExpression)expression).GetValueFromMemberExpression());
            //If a method is being performed (ex. ToLower()) we just want the name and not the method
            else if (expression is MethodCallExpression)
            {
                //We cast the expression operand to MethodCallExpression so we can cast the Object to MemberExpression in which we can get the value
                key += string.Format(" {0} ", ((MethodCallExpression)expression).GetPropertyNameFromMethodCallExpression());
            }
            else
            {
                //we cast the left side to a binary expression and call this method again so we can dig down the expression tree and get what we want
                var binaryExpressionNested = (BinaryExpression)expression;
                key = binaryExpressionNested.GetKey(key);
            }

            return key;
        }

        private static string GetValueFromMemberExpression(this MemberExpression expression)
        {
            if (expression.Expression is ConstantExpression)
                return ((FieldInfo)(expression.Member)).GetValue(((ConstantExpression)expression.Expression).Value).ToString();

            return expression.Member.Name;
        }

        private static string GetValueFromBinaryExpressionOperands(this Expression expression)
        {
            string result;
            if (expression is MemberExpression)
                result = ((MemberExpression)expression).GetValueFromMemberExpression();
            else if (expression is ConstantExpression)
            {
                var constExpression = (ConstantExpression)expression;
                result = constExpression.Value == null ? "null" : constExpression.Value.ToString();
            }
            else
                result = expression.ToString();

            return result;
        }
    }
}
